package com.example.common.utils.jwt;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.sys.entity.UserInfo;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class JwtUtils {
    private static final Logger log = org.slf4j.LoggerFactory.getLogger(JwtUtils.class);
    
    // cookie 或者 header 中的key
    private static final String JwtTokenKey = "token";
    
    // only-save-in-server-secret
    // 仅服务器持有的加密密钥(其实是加密算法的一个加盐)
    private static final String AlgorithmSecret = "this is secret";
    private static final Algorithm ALGORITHM_HMAC256 = Algorithm.HMAC256(AlgorithmSecret);
    
    // 一些加密算法需要提供公钥和私钥
    private static final String AlgorithmPublicKey = "this is public";
    private static final String AlgorithmPrivateKey = "this is private";
    
    //
    private static final String IssuerName = "SZXF-YWTB";
    
    /**
     * 创建 token
     * JWT TOKEN 由三部分组成： Header, Payload, Signature
     * Header:    头部信息。 包含 token 类型（JWT） 和 签名算法（一般为 HS256， 即 HMAC SHA256）
     * Payload:   有效载荷。 载荷指相关业务信息。 一般包含用户名等信息， 但是不要包含 密码等私密信息。 因为载荷是可以直接解密出明文的
     * Signature: 签名。 在服务端自定义一个私钥（任意字符串， 但是只能服务器持有， 这个 secret 的作用是加密， 避免 token 被篡改）
     *
     * @param userInfo
     * @return
     */
    public static String createJWTToken(UserInfo userInfo, Integer expireMinutes) {
        // 服务端 加密密钥 以及 加密算法
        Algorithm algorithm = Algorithm.HMAC256(AlgorithmSecret);
        
        // jwt 头部信息
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256"); // 签名算法
        map.put("typ", "JWT"); // token 类型（自声明）
        
        // 签发时间 和 过期时间
        Date nowDate = new Date();
        if (expireMinutes == null || expireMinutes <= 0) {
            expireMinutes = 1;
        }
        //Date expireDate = AddDate(nowDate,2*60);//120 分钟过期
        Date expireDate = AddDate(nowDate, expireMinutes);//1 分钟过期
        
        // 用户信息
        String userInfoString = JSON.toJSONString(userInfo);
        
        // 自定义有效负载 withPayload 最新版本好像不可用
        //Map<String, Object> payloadClaims = new HashMap<>();
        //payloadClaims.put("@context", "https://auth0.com/");
        //String token = JWT.create()
        //        .withPayload(payloadClaims)
        //        .sign(algorithm);
        
        // 自定义头部声明
        // 在下面使用 withHeader 方法 注入
        // withHeader 和 withClaim 可以多次链式调用， 是增加的方式
        HashMap<String, Object> headerClaims = new HashMap<>();
        headerClaims.put("owner", "SZXF-YWTB");
        headerClaims.put("desc", "这是一段描述信息");
    
        //JWTCreationException
        String token = JWT.create()
                .withHeader(map) // 头部信息；对应 token 的 header 部分
                .withIssuer(IssuerName) // 签发人; 对应 payload.iss
                //.withClaim("name", userInfo.getNickname()) // 一般指用户名； 对应 payload.name
                //.withClaim("username", userInfo.getUsername())
                //.withClaim("loginName", userInfo.getUsername())
                //.withClaim("userId", userInfo.getId().toString())
                //.withClaim("userInfo", userInfoString)
                //.withArrayClaim("someIds", new Integer[]{1, 2, 3})
                .withClaim("userid", userInfo.getId())
                .withClaim("username", userInfo.getUsername())
                .withClaim("name", userInfo.getNickname())
                .withAudience("Client") // 受众；对应 payload.aud
                .withSubject("SZXF AUTHORIZATION JWT TOKEN") // 主题；对应 payload.sub
                .withIssuedAt(nowDate) // 颁发时间；对应 payload.iat
                //.withNotBefore(nowDate) // 生效时间；对应 payload.nbf。
                .withExpiresAt(expireDate) // 过期时间；对应 payload.exp。
                .withHeader(headerClaims)
                .sign(algorithm);// 使用服务端持有 secret 加密
    
        // 验证令牌时，时间验证（iat, nbf, exp）会自动发生，导致在值无效时被抛出。如果缺少前面的任何字段，则在此验证中将不考虑这些字段。
        // JWTVerificationException
        
        return token;
        
        //return JWT.create().withHeader().withAudience(user.getId().toString(),user.getNickname()).withIssuedAt(new Date())
        //            .sign(Algorithm.HMAC256(user.getPassword()));
    }
    
    /**
     * 创建一个 token
     * 使用默认的过期时间 （30分钟）
     * @param userInfo
     * @return
     */
    public static String createJWTToken(UserInfo userInfo) {
        return createJWTToken(userInfo, 30);
    }
    
    
    /**
     * 创建一个 1分钟 后过期的 token
     * @param userInfo
     * @return
     */
    public static String createJWTTokenExpireOneMinute(UserInfo userInfo) {
        return createJWTToken(userInfo, 1);
    }
    
    public static String createJWTTokenExpireOneHour(UserInfo userInfo) {
        return createJWTToken(userInfo, 60);
    }
    
    public static String createJWTTokenExpireSixHour(UserInfo userInfo) {
        return createJWTToken(userInfo, 60*6);
    }
    
    public static String createJWTTokenExpireOneDay(UserInfo userInfo) {
        return createJWTToken(userInfo, 60*24);
    }
    
    /**
     * 创建一个 一周 后过期的 token
     * @param userInfo
     * @return
     */
    public static String createJWTTokenExpireOneWeek(UserInfo userInfo) {
        return createJWTToken(userInfo, 7*24*60);
    }
    
    /**
     * JWTVerifier 实例通常是可重用的
     * 因此可以让其由 Spring Bean 管理
     * @return
     */
    public static JWTVerifier createVerifier() {
        Algorithm algorithm = Algorithm.HMAC256(AlgorithmSecret);
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(IssuerName).build();
    
        // 若要指定仍应将令牌视为有效的回旋余地窗口，请使用构建器中的方法并传递正秒值。这适用于上面列出的每个项目。
        // acceptLeeway()JWTVerifier
/*        JWTVerifier verifier = JWT.require(algorithm)
                .acceptLeeway(1) // 1 sec for nbf, iat and exp
                .build();*/
        
        return verifier;
    }
    
    /**
     * 验证 token
     */
    public static void verifyJWTToken(String token) {
        // Token 校验器
        // 可以根据 创建 token 时 的参数（payload 部分信息）来指定一个或者多个条件
        Algorithm algorithm = Algorithm.HMAC256(AlgorithmSecret);
        
        // 此处服务器简单验证了该 token 是否是自己声明的机构签发的
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(IssuerName).build();
        
        // 使用 校验器 解析(解码) token
        // verify token 时如果不满足以上指定的任意条件将会抛出运行时异常
        // 方法中不需要 throws 但是需要在用户认证时最好进行try-catch处理以返回友好的错误信息给前端
        // JWTVerificationException包括多个子类
        DecodedJWT jwt = verifier.verify(token);
        
        // 解析成功
        // 现在可以获取 token 中的所有信息
        
        // 其实前后端都可以直接解析 jwt 的内容
        //jwt = JWT.decode(token);
        //String subject = jwt.getSubject();
        //Map<String, Claim> claims = jwt.getClaims();
        //Claim claim = claims.get("loginName");
        //System.out.println("自定义 claim：" + claim.asString());
        //
        //List<String> audience = jwt.getAudience();
        //System.out.println("subject 值：" + subject);
        //System.out.println("audience 值：" + audience.get(0));
    }
    
    /**
     * 仅仅解码 JWT
     * @param jwtToken
     * @return
     */
    public static DecodedJWT decodedJWTToken(String jwtToken) {
        try {
            DecodedJWT jwt = JWT.decode(jwtToken);
            
            
    
            return jwt;
        } catch (JWTDecodeException e) {
            //e.printStackTrace();
            log.error("JWT token 解码失败 - " + jwtToken);
        }
        return null;
    }
    
    /**
     * 此方法演示如何解析 token 信息
     * 如果需要解析token，可以将其封装为一个自定义的对象
     * 由于 jwt 格式其实可以包含一些自定义字段
     * 因此 封装类 需要由 后端自己设计， 直接使用JWT提供的对象会造成部分信息无法解析而不完整
     * @param jwt
     */
    public static void parseJWT(DecodedJWT jwt) {
        // 解析 token 细节
        // 可以将其封装为一个 对象 返回
        
        String contentType = jwt.getContentType();
    
        // header 信息
        String algorithm = jwt.getAlgorithm();
        String type = jwt.getType();
        Claim owner = jwt.getHeaderClaim("owner");
    
        // 有效负载信息
        String issuer = jwt.getIssuer();
        String subject = jwt.getSubject();
        List<String> audience = jwt.getAudience();
        Date expiresAt = jwt.getExpiresAt();
        Date notBefore = jwt.getNotBefore();
        Date issuedAt = jwt.getIssuedAt();
        String id = jwt.getId();
        
        // 有效负载中的 私人信息（用户自定义）
        // 如何解析由用户自己确保（类型必须和创建 token 时一致）
        Map<String, Claim> claims = jwt.getClaims();
        Claim isAdmin = claims.get("isAdmin");
        Boolean isAdminValue = isAdmin.asBoolean();
        Claim isAdmin1 = jwt.getClaim("isAdmin");
        
        
        
    
        // 签名字符串
        String signature = jwt.getSignature();
        
        return;
    }
    
    public static boolean isJWTNeedUpdate(String jwtToken) {
        String token = jwtToken;
        // 获取token过期时间
        Date expiresAt = null;
        try {
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(AlgorithmSecret))
                    .withIssuer(IssuerName)
                    .build()
                    .verify(token);
            expiresAt = jwt.getExpiresAt();
        } catch (TokenExpiredException e) {
            return true;
        } catch (JWTVerificationException e) {
            throw new JWTVerificationException("JWT 校验失败");
        }
        
        // 剩余时间小于 100 s
        boolean flag = (expiresAt.getTime()-System.currentTimeMillis()) < 100;
        return flag;
    }
    
    /**
     * 上面方法的重载方法
     * @param jwtToken
     */
    public static void parseJWT(String jwtToken) {
        DecodedJWT decodedJWT = decodedJWTToken(jwtToken);
        if (decodedJWT == null) {
            return;
        }
        parseJWT(decodedJWT);
        return;
    }
    
    /**
     * 时间加减法
     */
    private static Date AddDate(Date date, Integer minute) {
        if (null == date)
            date = new Date();
        Calendar cal = new GregorianCalendar();
        cal.setTime(date);
        cal.add(Calendar.MINUTE, minute);
        return cal.getTime();
        
    }
    
    public static void main(String[] args) {
        String token = createJWTToken(getUserInfo());
        System.out.println(token);
        
        Algorithm algorithm = Algorithm.HMAC256(AlgorithmSecret);
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(IssuerName).build();
        DecodedJWT jwt = verifier.verify(token);
    
        System.out.println(JSON.toJSONString(jwt, true));
        //System.out.println(JSON.toJSONString(jwt.getClaims(), true));
    
        for (Map.Entry<String, Claim> claim : jwt.getClaims().entrySet()) {
            System.out.println(claim.getKey() + ": " + claim.getValue().asString());
        }
        
        //UserInfo userInfo = JSON.parseObject(jwt.getClaims().get("userInfo").asString(), UserInfo.class);
        //System.out.println(userInfo);
        //System.out.println(jwt.getClaims().get("exp").asDate());
    
        //String subject = jwt.getSubject();
        //Map<String, Claim> claims = jwt.getClaims();
        //Claim claim = claims.get("loginName");
        //System.out.println("自定义 claim：" + claim.asString());
        //List<String> audience = jwt.getAudience();
        //System.out.println("subject 值：" + subject);
        //System.out.println("audience 值：" + audience.get(0));
    }
    
    private static UserInfo getUserInfo() {
        return new UserInfo(1, "admin", "123456", "管理员", "admin@hs.com");
    }
    
}
